convex realtime

安装量: 109
排名: #7784

安装

npx skills add https://github.com/waynesutton/convexskills --skill convex-realtime

Convex Realtime Build reactive applications with Convex's real-time subscriptions, optimistic updates, intelligent caching, and cursor-based pagination. Documentation Sources Before implementing, do not assume; fetch the latest documentation: Primary: https://docs.convex.dev/client/react Optimistic Updates: https://docs.convex.dev/client/react/optimistic-updates Pagination: https://docs.convex.dev/database/pagination For broader context: https://docs.convex.dev/llms.txt Instructions How Convex Realtime Works Automatic Subscriptions - useQuery creates a subscription that updates automatically Smart Caching - Query results are cached and shared across components Consistency - All subscriptions see a consistent view of the database Efficient Updates - Only re-renders when relevant data changes Basic Subscriptions // React component with real-time data import { useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function TaskList ( { userId } : { userId : Id < "users"

} ) { // Automatically subscribes and updates in real-time const tasks = useQuery ( api . tasks . list , { userId } ) ; if ( tasks === undefined ) { return < div

Loading ... < / div

; } return ( < ul

{ tasks . map ( ( task ) => ( < li key = { task . _id }

{ task . title } < / li

) ) } < / ul

) ; } Conditional Queries import { useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function UserProfile ( { userId } : { userId : Id < "users"

| null } ) { // Skip query when userId is null const user = useQuery ( api . users . get , userId ? { userId } : "skip" ) ; if ( userId === null ) { return < div

Select a user < / div

; } if ( user === undefined ) { return < div

Loading ... < / div

; } return < div

{ user . name } < / div

; } Mutations with Real-time Updates import { useMutation , useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function TaskManager ( { userId } : { userId : Id < "users"

} ) { const tasks = useQuery ( api . tasks . list , { userId } ) ; const createTask = useMutation ( api . tasks . create ) ; const toggleTask = useMutation ( api . tasks . toggle ) ; const handleCreate = async ( title : string ) => { // Mutation triggers automatic re-render when data changes await createTask ( { title , userId } ) ; } ; const handleToggle = async ( taskId : Id < "tasks"

) => { await toggleTask ( { taskId } ) ; } ; return ( < div

< button onClick = { ( ) => handleCreate ( "New Task" ) }

Add Task < / button

< ul

{ tasks ?. map ( ( task ) => ( < li key = { task . _id } onClick = { ( ) => handleToggle ( task . _id ) }

{ task . completed ? "✓" : "○" } { task . title } < / li

) ) } < / ul

< / div

) ; } Optimistic Updates Show changes immediately before server confirmation: import { useMutation , useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; import { Id } from "../convex/_generated/dataModel" ; function TaskItem ( { task } : { task : Task } ) { const toggleTask = useMutation ( api . tasks . toggle ) . withOptimisticUpdate ( ( localStore , args ) => { const { taskId } = args ; const currentValue = localStore . getQuery ( api . tasks . get , { taskId } ) ; if ( currentValue !== undefined ) { localStore . setQuery ( api . tasks . get , { taskId } , { ... currentValue , completed : ! currentValue . completed , } ) ; } } ) ; return ( < div onClick = { ( ) => toggleTask ( { taskId : task . _id } ) }

{ task . completed ? "✓" : "○" } { task . title } < / div

) ; } Optimistic Updates for Lists import { useMutation } from "convex/react" ; import { api } from "../convex/_generated/api" ; function useCreateTask ( userId : Id < "users"

) { return useMutation ( api . tasks . create ) . withOptimisticUpdate ( ( localStore , args ) => { const { title , userId } = args ; const currentTasks = localStore . getQuery ( api . tasks . list , { userId } ) ; if ( currentTasks !== undefined ) { // Add optimistic task to the list const optimisticTask = { _id : crypto . randomUUID ( ) as Id < "tasks"

, _creationTime : Date . now ( ) , title , userId , completed : false , } ; localStore . setQuery ( api . tasks . list , { userId } , [ optimisticTask , ... currentTasks , ] ) ; } } ) ; } Cursor-Based Pagination // convex/messages.ts import { query } from "./_generated/server" ; import { v } from "convex/values" ; import { paginationOptsValidator } from "convex/server" ; export const listPaginated = query ( { args : { channelId : v . id ( "channels" ) , paginationOpts : paginationOptsValidator , } , handler : async ( ctx , args ) => { return await ctx . db . query ( "messages" ) . withIndex ( "by_channel" , ( q ) => q . eq ( "channelId" , args . channelId ) ) . order ( "desc" ) . paginate ( args . paginationOpts ) ; } , } ) ; // React component with pagination import { usePaginatedQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function MessageList ( { channelId } : { channelId : Id < "channels"

} ) { const { results , status , loadMore } = usePaginatedQuery ( api . messages . listPaginated , { channelId } , { initialNumItems : 20 } ) ; return ( < div

{ results . map ( ( message ) => ( < div key = { message . _id }

{ message . content } < / div

) ) } { status === "CanLoadMore" && ( < button onClick = { ( ) => loadMore ( 20 ) }

Load More < / button

) } { status === "LoadingMore" && < div

Loading ... < / div

} { status === "Exhausted" && < div

No more messages < / div

} < / div

) ; } Infinite Scroll Pattern import { usePaginatedQuery } from "convex/react" ; import { useEffect , useRef } from "react" ; import { api } from "../convex/_generated/api" ; function InfiniteMessageList ( { channelId } : { channelId : Id < "channels"

} ) { const { results , status , loadMore } = usePaginatedQuery ( api . messages . listPaginated , { channelId } , { initialNumItems : 20 } ) ; const observerRef = useRef < IntersectionObserver

( ) ; const loadMoreRef = useRef < HTMLDivElement

( null ) ; useEffect ( ( ) => { if ( observerRef . current ) { observerRef . current . disconnect ( ) ; } observerRef . current = new IntersectionObserver ( ( entries ) => { if ( entries [ 0 ] . isIntersecting && status === "CanLoadMore" ) { loadMore ( 20 ) ; } } ) ; if ( loadMoreRef . current ) { observerRef . current . observe ( loadMoreRef . current ) ; } return ( ) => observerRef . current ?. disconnect ( ) ; } , [ status , loadMore ] ) ; return ( < div

{ results . map ( ( message ) => ( < div key = { message . _id }

{ message . content } < / div

) ) } < div ref = { loadMoreRef } style = { { height : 1 } } /

{ status === "LoadingMore" && < div

Loading ... < / div

} < / div

) ; } Multiple Subscriptions import { useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function Dashboard ( { userId } : { userId : Id < "users"

} ) { // Multiple subscriptions update independently const user = useQuery ( api . users . get , { userId } ) ; const tasks = useQuery ( api . tasks . list , { userId } ) ; const notifications = useQuery ( api . notifications . unread , { userId } ) ; const isLoading = user === undefined || tasks === undefined || notifications === undefined ; if ( isLoading ) { return < div

Loading ... < / div

; } return ( < div

< h1

Welcome , { user . name } < / h1

< p

You have { tasks . length } tasks < / p

< p

{ notifications . length } unread notifications < / p

< / div

) ; } Examples Real-time Chat Application // convex/messages.ts import { query , mutation } from "./_generated/server" ; import { v } from "convex/values" ; export const list = query ( { args : { channelId : v . id ( "channels" ) } , returns : v . array ( v . object ( { _id : v . id ( "messages" ) , _creationTime : v . number ( ) , content : v . string ( ) , authorId : v . id ( "users" ) , authorName : v . string ( ) , } ) ) , handler : async ( ctx , args ) => { const messages = await ctx . db . query ( "messages" ) . withIndex ( "by_channel" , ( q ) => q . eq ( "channelId" , args . channelId ) ) . order ( "desc" ) . take ( 100 ) ; // Enrich with author names return Promise . all ( messages . map ( async ( msg ) => { const author = await ctx . db . get ( msg . authorId ) ; return { ... msg , authorName : author ?. name ?? "Unknown" , } ; } ) ) ; } , } ) ; export const send = mutation ( { args : { channelId : v . id ( "channels" ) , authorId : v . id ( "users" ) , content : v . string ( ) , } , returns : v . id ( "messages" ) , handler : async ( ctx , args ) => { return await ctx . db . insert ( "messages" , { channelId : args . channelId , authorId : args . authorId , content : args . content , } ) ; } , } ) ; // ChatRoom.tsx import { useQuery , useMutation } from "convex/react" ; import { api } from "../convex/_generated/api" ; import { useState , useRef , useEffect } from "react" ; function ChatRoom ( { channelId , userId } : Props ) { const messages = useQuery ( api . messages . list , { channelId } ) ; const sendMessage = useMutation ( api . messages . send ) ; const [ input , setInput ] = useState ( "" ) ; const messagesEndRef = useRef < HTMLDivElement

( null ) ; // Auto-scroll to bottom on new messages useEffect ( ( ) => { messagesEndRef . current ?. scrollIntoView ( { behavior : "smooth" } ) ; } , [ messages ] ) ; const handleSend = async ( e : React . FormEvent ) => { e . preventDefault ( ) ; if ( ! input . trim ( ) ) return ; await sendMessage ( { channelId , authorId : userId , content : input . trim ( ) , } ) ; setInput ( "" ) ; } ; return ( < div className = "chat-room"

< div className = "messages"

{ messages ?. map ( ( msg ) => ( < div key = { msg . _id } className = "message"

< strong

{ msg . authorName } : < / strong

{ msg . content } < / div

) ) } < div ref = { messagesEndRef } /

< / div

< form onSubmit = { handleSend }

< input value = { input } onChange = { ( e ) => setInput ( e . target . value ) } placeholder = "Type a message..." /

< button type = "submit"

Send < / button

< / form

< / div

) ; } Best Practices Never run npx convex deploy unless explicitly instructed Never run any git commands unless explicitly instructed Use "skip" for conditional queries instead of conditionally calling hooks Implement optimistic updates for better perceived performance Use usePaginatedQuery for large datasets Handle undefined state (loading) explicitly Avoid unnecessary re-renders by memoizing derived data Common Pitfalls Conditional hook calls - Use "skip" instead of if statements Not handling loading state - Always check for undefined Missing optimistic update rollback - Optimistic updates auto-rollback on error Over-fetching with pagination - Use appropriate page sizes Ignoring subscription cleanup - React handles this automatically References Convex Documentation: https://docs.convex.dev/ Convex LLMs.txt: https://docs.convex.dev/llms.txt React Client: https://docs.convex.dev/client/react Optimistic Updates: https://docs.convex.dev/client/react/optimistic-updates Pagination: https://docs.convex.dev/database/pagination

返回排行榜